{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 第7章 支持向量机"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1．支持向量机最简单的情况是线性可分支持向量机，或硬间隔支持向量机。构建它的条件是训练数据线性可分。其学习策略是最大间隔法。可以表示为凸二次规划问题，其原始最优化问题为\n",
    "\n",
    "$$\\min _{w, b} \\frac{1}{2}\\|w\\|^{2}$$\n",
    "\n",
    "$$s.t. \\quad y_{i}\\left(w \\cdot x_{i}+b\\right)-1 \\geqslant 0, \\quad i=1,2, \\cdots, N$$\n",
    "\n",
    "求得最优化问题的解为$w^*$，$b^*$，得到线性可分支持向量机，分离超平面是\n",
    "\n",
    "$$w^{*} \\cdot x+b^{*}=0$$\n",
    "\n",
    "分类决策函数是\n",
    "\n",
    "$$f(x)=\\operatorname{sign}\\left(w^{*} \\cdot x+b^{*}\\right)$$\n",
    "\n",
    "最大间隔法中，函数间隔与几何间隔是重要的概念。\n",
    "\n",
    "线性可分支持向量机的最优解存在且唯一。位于间隔边界上的实例点为支持向量。最优分离超平面由支持向量完全决定。\n",
    "二次规划问题的对偶问题是\n",
    "$$\\min \\frac{1}{2} \\sum_{i=1}^{N} \\sum_{j=1}^{N} \\alpha_{i} \\alpha_{j} y_{i} y_{j}\\left(x_{i} \\cdot x_{j}\\right)-\\sum_{i=1}^{N} \\alpha_{i}$$\n",
    "\n",
    "$$s.t. \\quad \\sum_{i=1}^{N} \\alpha_{i} y_{i}=0$$\n",
    "\n",
    "$$\\alpha_{i} \\geqslant 0, \\quad i=1,2, \\cdots, N$$\n",
    "\n",
    "通常，通过求解对偶问题学习线性可分支持向量机，即首先求解对偶问题的最优值\n",
    " \n",
    "$a^*$，然后求最优值$w^*$和$b^*$，得出分离超平面和分类决策函数。\n",
    "\n",
    "2．现实中训练数据是线性可分的情形较少，训练数据往往是近似线性可分的，这时使用线性支持向量机，或软间隔支持向量机。线性支持向量机是最基本的支持向量机。\n",
    "\n",
    "对于噪声或例外，通过引入松弛变量$\\xi_{\\mathrm{i}}$，使其“可分”，得到线性支持向量机学习的凸二次规划问题，其原始最优化问题是\n",
    "\n",
    "$$\\min _{w, b, \\xi} \\frac{1}{2}\\|w\\|^{2}+C \\sum_{i=1}^{N} \\xi_{i}$$\n",
    "\n",
    "$$s.t. \\quad y_{i}\\left(w \\cdot x_{i}+b\\right) \\geqslant 1-\\xi_{i}, \\quad i=1,2, \\cdots, N$$\n",
    "\n",
    "$$\\xi_{i} \\geqslant 0, \\quad i=1,2, \\cdots, N$$\n",
    "\n",
    "求解原始最优化问题的解$w^*$和$b^*$，得到线性支持向量机，其分离超平面为\n",
    "\n",
    "$$w^{*} \\cdot x+b^{*}=0$$\n",
    "\n",
    "分类决策函数为\n",
    "\n",
    "$$f(x)=\\operatorname{sign}\\left(w^{*} \\cdot x+b^{*}\\right)$$\n",
    "\n",
    "线性可分支持向量机的解$w^*$唯一但$b^*$不唯一。对偶问题是\n",
    "\n",
    "$$\\min _{\\alpha} \\frac{1}{2} \\sum_{i=1}^{N} \\sum_{j=1}^{N} \\alpha_{i} \\alpha_{j} y_{i} y_{j}\\left(x_{i} \\cdot x_{j}\\right)-\\sum_{i=1}^{N} \\alpha_{i}$$\n",
    "\n",
    "$$s.t. \\quad \\sum_{i=1}^{N} \\alpha_{i} y_{i}=0$$\n",
    "\n",
    "$$0 \\leqslant \\alpha_{i} \\leqslant C, \\quad i=1,2, \\cdots, N$$\n",
    "\n",
    "线性支持向量机的对偶学习算法，首先求解对偶问题得到最优解$\\alpha^*$，然后求原始问题最优解$w^*$和$b^*$，得出分离超平面和分类决策函数。\n",
    "\n",
    "对偶问题的解$\\alpha^*$中满$\\alpha_{i}^{*}>0$的实例点$x_i$称为支持向量。支持向量可在间隔边界上，也可在间隔边界与分离超平面之间，或者在分离超平面误分一侧。最优分离超平面由支持向量完全决定。\n",
    "\n",
    "线性支持向量机学习等价于最小化二阶范数正则化的合页函数\n",
    "\n",
    "$$\\sum_{i=1}^{N}\\left[1-y_{i}\\left(w \\cdot x_{i}+b\\right)\\right]_{+}+\\lambda\\|w\\|^{2}$$\n",
    "\n",
    "3．非线性支持向量机\n",
    "\n",
    "对于输入空间中的非线性分类问题，可以通过非线性变换将它转化为某个高维特征空间中的线性分类问题，在高维特征空间中学习线性支持向量机。由于在线性支持向量机学习的对偶问题里，目标函数和分类决策函数都只涉及实例与实例之间的内积，所以不需要显式地指定非线性变换，而是用核函数来替换当中的内积。核函数表示，通过一个非线性转换后的两个实例间的内积。具体地，$K(x,z)$是一个核函数，或正定核，意味着存在一个从输入空间x到特征空间的映射$\\mathcal{X} \\rightarrow \\mathcal{H}$，对任意$\\mathcal{X}$，有\n",
    "\n",
    "$$K(x, z)=\\phi(x) \\cdot \\phi(z)$$\n",
    "\n",
    "对称函数$K(x,z)$为正定核的充要条件如下：对任意$$\\mathrm{x}_{\\mathrm{i}} \\in \\mathcal{X}, \\quad \\mathrm{i}=1,2, \\ldots, \\mathrm{m}$$，任意正整数$m$，对称函数$K(x,z)$对应的Gram矩阵是半正定的。\n",
    "\n",
    "所以，在线性支持向量机学习的对偶问题中，用核函数$K(x,z)$替代内积，求解得到的就是非线性支持向量机\n",
    "\n",
    "$$f(x)=\\operatorname{sign}\\left(\\sum_{i=1}^{N} \\alpha_{i}^{*} y_{i} K\\left(x, x_{i}\\right)+b^{*}\\right)$$\n",
    "\n",
    "4．SMO算法\n",
    "\n",
    "SMO算法是支持向量机学习的一种快速算法，其特点是不断地将原二次规划问题分解为只有两个变量的二次规划子问题，并对子问题进行解析求解，直到所有变量满足KKT条件为止。这样通过启发式的方法得到原二次规划问题的最优解。因为子问题有解析解，所以每次计算子问题都很快，虽然计算子问题次数很多，但在总体上还是高效的。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----\n",
    "分离超平面：$w^Tx+b=0$\n",
    "\n",
    "点到直线距离：$r=\\frac{|w^Tx+b|}{||w||_2}$\n",
    "\n",
    "$||w||_2$为2-范数：$||w||_2=\\sqrt[2]{\\sum^m_{i=1}w_i^2}$\n",
    "\n",
    "直线为超平面，样本可表示为：\n",
    "\n",
    "$w^Tx+b\\ \\geq+1$\n",
    "\n",
    "$w^Tx+b\\ \\leq+1$\n",
    "\n",
    "#### margin：\n",
    "\n",
    "**函数间隔**：$label(w^Tx+b)\\ or\\ y_i(w^Tx+b)$\n",
    "\n",
    "**几何间隔**：$r=\\frac{label(w^Tx+b)}{||w||_2}$，当数据被正确分类时，几何间隔就是点到超平面的距离\n",
    "\n",
    "为了求几何间隔最大，SVM基本问题可以转化为求解:($\\frac{r^*}{||w||}$为几何间隔，(${r^*}$为函数间隔)\n",
    "\n",
    "$$\\max\\ \\frac{r^*}{||w||}$$\n",
    "\n",
    "$$(subject\\ to)\\ y_i({w^T}x_i+{b})\\geq {r^*},\\ i=1,2,..,m$$\n",
    "\n",
    "分类点几何间隔最大，同时被正确分类。但这个方程并非凸函数求解，所以要先①将方程转化为凸函数，②用拉格朗日乘子法和KKT条件求解对偶问题。\n",
    "\n",
    "①转化为凸函数：\n",
    "\n",
    "先令${r^*}=1$，方便计算（参照衡量，不影响评价结果）\n",
    "\n",
    "$$\\max\\ \\frac{1}{||w||}$$\n",
    "\n",
    "$$s.t.\\ y_i({w^T}x_i+{b})\\geq {1},\\ i=1,2,..,m$$\n",
    "\n",
    "再将$\\max\\ \\frac{1}{||w||}$转化成$\\min\\ \\frac{1}{2}||w||^2$求解凸函数，1/2是为了求导之后方便计算。\n",
    "\n",
    "$$\\min\\ \\frac{1}{2}||w||^2$$\n",
    "\n",
    "$$s.t.\\ y_i(w^Tx_i+b)\\geq 1,\\ i=1,2,..,m$$\n",
    "\n",
    "②用拉格朗日乘子法和KKT条件求解最优值：\n",
    "\n",
    "$$\\min\\ \\frac{1}{2}||w||^2$$\n",
    "\n",
    "$$s.t.\\ -y_i(w^Tx_i+b)+1\\leq 0,\\ i=1,2,..,m$$\n",
    "\n",
    "整合成：\n",
    "\n",
    "$$L(w, b, \\alpha) = \\frac{1}{2}||w||^2+\\sum^m_{i=1}\\alpha_i(-y_i(w^Tx_i+b)+1)$$\n",
    "\n",
    "推导：$\\min\\ f(x)=\\min \\max\\ L(w, b, \\alpha)\\geq \\max \\min\\ L(w, b, \\alpha)$\n",
    "\n",
    "根据KKT条件：\n",
    "\n",
    "$$\\frac{\\partial }{\\partial w}L(w, b, \\alpha)=w-\\sum\\alpha_iy_ix_i=0,\\ w=\\sum\\alpha_iy_ix_i$$\n",
    "\n",
    "$$\\frac{\\partial }{\\partial b}L(w, b, \\alpha)=\\sum\\alpha_iy_i=0$$\n",
    "\n",
    "代入$ L(w, b, \\alpha)$\n",
    "\n",
    "$\\min\\  L(w, b, \\alpha)=\\frac{1}{2}||w||^2+\\sum^m_{i=1}\\alpha_i(-y_i(w^Tx_i+b)+1)$\n",
    "\n",
    "$\\qquad\\qquad\\qquad=\\frac{1}{2}w^Tw-\\sum^m_{i=1}\\alpha_iy_iw^Tx_i-b\\sum^m_{i=1}\\alpha_iy_i+\\sum^m_{i=1}\\alpha_i$\n",
    "\n",
    "$\\qquad\\qquad\\qquad=\\frac{1}{2}w^T\\sum\\alpha_iy_ix_i-\\sum^m_{i=1}\\alpha_iy_iw^Tx_i+\\sum^m_{i=1}\\alpha_i$\n",
    "\n",
    "$\\qquad\\qquad\\qquad=\\sum^m_{i=1}\\alpha_i-\\frac{1}{2}\\sum^m_{i=1}\\alpha_iy_iw^Tx_i$\n",
    "\n",
    "$\\qquad\\qquad\\qquad=\\sum^m_{i=1}\\alpha_i-\\frac{1}{2}\\sum^m_{i,j=1}\\alpha_i\\alpha_jy_iy_j(x_ix_j)$\n",
    "\n",
    "再把max问题转成min问题：\n",
    "\n",
    "$\\max\\ \\sum^m_{i=1}\\alpha_i-\\frac{1}{2}\\sum^m_{i,j=1}\\alpha_i\\alpha_jy_iy_j(x_ix_j)=\\min \\frac{1}{2}\\sum^m_{i,j=1}\\alpha_i\\alpha_jy_iy_j(x_ix_j)-\\sum^m_{i=1}\\alpha_i$\n",
    "\n",
    "$s.t.\\ \\sum^m_{i=1}\\alpha_iy_i=0,$\n",
    "\n",
    "$ \\alpha_i \\geq 0,i=1,2,...,m$\n",
    "\n",
    "以上为SVM对偶问题的对偶形式\n",
    "\n",
    "-----\n",
    "#### kernel\n",
    "\n",
    "在低维空间计算获得高维空间的计算结果，也就是说计算结果满足高维（满足高维，才能说明高维下线性可分）。\n",
    "\n",
    "#### soft margin & slack variable\n",
    "\n",
    "引入松弛变量$\\xi\\geq0$，对应数据点允许偏离的functional margin 的量。\n",
    "\n",
    "目标函数：\n",
    "\n",
    "$$\\min\\ \\frac{1}{2}||w||^2+C\\sum\\xi_i\\qquad s.t.\\ y_i(w^Tx_i+b)\\geq1-\\xi_i$$ \n",
    "\n",
    "对偶问题：\n",
    "\n",
    "$$\\max\\ \\sum^m_{i=1}\\alpha_i-\\frac{1}{2}\\sum^m_{i,j=1}\\alpha_i\\alpha_jy_iy_j(x_ix_j)=\\min \\frac{1}{2}\\sum^m_{i,j=1}\\alpha_i\\alpha_jy_iy_j(x_ix_j)-\\sum^m_{i=1}\\alpha_i$$\n",
    "\n",
    "$$s.t.\\ C\\geq\\alpha_i \\geq 0,i=1,2,...,m\\quad \\sum^m_{i=1}\\alpha_iy_i=0,$$\n",
    "\n",
    "-----\n",
    "\n",
    "#### Sequential Minimal Optimization\n",
    "\n",
    "首先定义特征到结果的输出函数：$u=w^Tx+b$.\n",
    "\n",
    "因为$w=\\sum\\alpha_iy_ix_i$\n",
    "\n",
    "有$u=\\sum y_i\\alpha_iK(x_i, x)-b$\n",
    "\n",
    "\n",
    "----\n",
    "\n",
    "$$\\max \\sum^m_{i=1}\\alpha_i-\\frac{1}{2}\\sum^m_{i=1}\\sum^m_{j=1}\\alpha_i\\alpha_jy_iy_j<\\phi(x_i)^T,\\phi(x_j)>$$\n",
    "\n",
    "$$s.t.\\ \\sum^m_{i=1}\\alpha_iy_i=0,$$\n",
    "\n",
    "$$ \\alpha_i \\geq 0,i=1,2,...,m$$\n",
    "\n",
    "-----\n",
    "参考资料：\n",
    "\n",
    "[1] :[Lagrange Multiplier and KKT](http://blog.csdn.net/xianlingmao/article/details/7919597)\n",
    "\n",
    "[2] :[推导SVM](https://my.oschina.net/dfsj66011/blog/517766)\n",
    "\n",
    "[3] :[机器学习算法实践-支持向量机(SVM)算法原理](http://pytlab.org/2017/08/15/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E7%AE%97%E6%B3%95%E5%AE%9E%E8%B7%B5-%E6%94%AF%E6%8C%81%E5%90%91%E9%87%8F%E6%9C%BA-SVM-%E7%AE%97%E6%B3%95%E5%8E%9F%E7%90%86/)\n",
    "\n",
    "[4] :[Python实现SVM](http://blog.csdn.net/wds2006sdo/article/details/53156589)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.datasets import load_iris\n",
    "from sklearn.model_selection import  train_test_split\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# data\n",
    "def create_data():\n",
    "    iris = load_iris()\n",
    "    df = pd.DataFrame(iris.data, columns=iris.feature_names)\n",
    "    df['label'] = iris.target\n",
    "    df.columns = [\n",
    "        'sepal length', 'sepal width', 'petal length', 'petal width', 'label'\n",
    "    ]\n",
    "    data = np.array(df.iloc[:100, [0, 1, -1]])\n",
    "    for i in range(len(data)):\n",
    "        if data[i, -1] == 0:\n",
    "            data[i, -1] = -1\n",
    "    # print(data)\n",
    "    return data[:, :2], data[:, -1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "X, y = create_data()\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x1d96e8af308>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.scatter(X[:50,0],X[:50,1], label='0')\n",
    "plt.scatter(X[50:,0],X[50:,1], label='1')\n",
    "plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SVM:\n",
    "    def __init__(self, max_iter=100, kernel='linear'):\n",
    "        self.max_iter = max_iter\n",
    "        self._kernel = kernel\n",
    "\n",
    "    def init_args(self, features, labels):\n",
    "        self.m, self.n = features.shape\n",
    "        self.X = features\n",
    "        self.Y = labels\n",
    "        self.b = 0.0\n",
    "\n",
    "        # 将Ei保存在一个列表里\n",
    "        self.alpha = np.ones(self.m)\n",
    "        self.E = [self._E(i) for i in range(self.m)]\n",
    "        # 松弛变量\n",
    "        self.C = 1.0\n",
    "\n",
    "    def _KKT(self, i):\n",
    "        y_g = self._g(i) * self.Y[i]\n",
    "        if self.alpha[i] == 0:\n",
    "            return y_g >= 1\n",
    "        elif 0 < self.alpha[i] < self.C:\n",
    "            return y_g == 1\n",
    "        else:\n",
    "            return y_g <= 1\n",
    "\n",
    "    # g(x)预测值，输入xi（X[i]）\n",
    "    def _g(self, i):\n",
    "        r = self.b\n",
    "        for j in range(self.m):\n",
    "            r += self.alpha[j] * self.Y[j] * self.kernel(self.X[i], self.X[j])\n",
    "        return r\n",
    "\n",
    "    # 核函数\n",
    "    def kernel(self, x1, x2):\n",
    "        if self._kernel == 'linear':\n",
    "            return sum([x1[k] * x2[k] for k in range(self.n)])\n",
    "        elif self._kernel == 'poly':\n",
    "            return (sum([x1[k] * x2[k] for k in range(self.n)]) + 1)**2\n",
    "\n",
    "        return 0\n",
    "\n",
    "    # E（x）为g(x)对输入x的预测值和y的差\n",
    "    def _E(self, i):\n",
    "        return self._g(i) - self.Y[i]\n",
    "\n",
    "    def _init_alpha(self):\n",
    "        # 外层循环首先遍历所有满足0<a<C的样本点，检验是否满足KKT\n",
    "        index_list = [i for i in range(self.m) if 0 < self.alpha[i] < self.C]\n",
    "        # 否则遍历整个训练集\n",
    "        non_satisfy_list = [i for i in range(self.m) if i not in index_list]\n",
    "        index_list.extend(non_satisfy_list)\n",
    "\n",
    "        for i in index_list:\n",
    "            if self._KKT(i):\n",
    "                continue\n",
    "\n",
    "            E1 = self.E[i]\n",
    "            # 如果E2是+，选择最小的；如果E2是负的，选择最大的\n",
    "            if E1 >= 0:\n",
    "                j = min(range(self.m), key=lambda x: self.E[x])\n",
    "            else:\n",
    "                j = max(range(self.m), key=lambda x: self.E[x])\n",
    "            return i, j\n",
    "\n",
    "    def _compare(self, _alpha, L, H):\n",
    "        if _alpha > H:\n",
    "            return H\n",
    "        elif _alpha < L:\n",
    "            return L\n",
    "        else:\n",
    "            return _alpha\n",
    "\n",
    "    def fit(self, features, labels):\n",
    "        self.init_args(features, labels)\n",
    "\n",
    "        for t in range(self.max_iter):\n",
    "            # train\n",
    "            i1, i2 = self._init_alpha()\n",
    "\n",
    "            # 边界\n",
    "            if self.Y[i1] == self.Y[i2]:\n",
    "                L = max(0, self.alpha[i1] + self.alpha[i2] - self.C)\n",
    "                H = min(self.C, self.alpha[i1] + self.alpha[i2])\n",
    "            else:\n",
    "                L = max(0, self.alpha[i2] - self.alpha[i1])\n",
    "                H = min(self.C, self.C + self.alpha[i2] - self.alpha[i1])\n",
    "\n",
    "            E1 = self.E[i1]\n",
    "            E2 = self.E[i2]\n",
    "            # eta=K11+K22-2K12\n",
    "            eta = self.kernel(self.X[i1], self.X[i1]) + self.kernel(\n",
    "                self.X[i2],\n",
    "                self.X[i2]) - 2 * self.kernel(self.X[i1], self.X[i2])\n",
    "            if eta <= 0:\n",
    "                # print('eta <= 0')\n",
    "                continue\n",
    "\n",
    "            alpha2_new_unc = self.alpha[i2] + self.Y[i2] * (\n",
    "                E1 - E2) / eta  #此处有修改，根据书上应该是E1 - E2，书上130-131页\n",
    "            alpha2_new = self._compare(alpha2_new_unc, L, H)\n",
    "\n",
    "            alpha1_new = self.alpha[i1] + self.Y[i1] * self.Y[i2] * (\n",
    "                self.alpha[i2] - alpha2_new)\n",
    "\n",
    "            b1_new = -E1 - self.Y[i1] * self.kernel(self.X[i1], self.X[i1]) * (\n",
    "                alpha1_new - self.alpha[i1]) - self.Y[i2] * self.kernel(\n",
    "                    self.X[i2],\n",
    "                    self.X[i1]) * (alpha2_new - self.alpha[i2]) + self.b\n",
    "            b2_new = -E2 - self.Y[i1] * self.kernel(self.X[i1], self.X[i2]) * (\n",
    "                alpha1_new - self.alpha[i1]) - self.Y[i2] * self.kernel(\n",
    "                    self.X[i2],\n",
    "                    self.X[i2]) * (alpha2_new - self.alpha[i2]) + self.b\n",
    "\n",
    "            if 0 < alpha1_new < self.C:\n",
    "                b_new = b1_new\n",
    "            elif 0 < alpha2_new < self.C:\n",
    "                b_new = b2_new\n",
    "            else:\n",
    "                # 选择中点\n",
    "                b_new = (b1_new + b2_new) / 2\n",
    "\n",
    "            # 更新参数\n",
    "            self.alpha[i1] = alpha1_new\n",
    "            self.alpha[i2] = alpha2_new\n",
    "            self.b = b_new\n",
    "\n",
    "            self.E[i1] = self._E(i1)\n",
    "            self.E[i2] = self._E(i2)\n",
    "        return 'train done!'\n",
    "\n",
    "    def predict(self, data):\n",
    "        r = self.b\n",
    "        for i in range(self.m):\n",
    "            r += self.alpha[i] * self.Y[i] * self.kernel(data, self.X[i])\n",
    "\n",
    "        return 1 if r > 0 else -1\n",
    "\n",
    "    def score(self, X_test, y_test):\n",
    "        right_count = 0\n",
    "        for i in range(len(X_test)):\n",
    "            result = self.predict(X_test[i])\n",
    "            if result == y_test[i]:\n",
    "                right_count += 1\n",
    "        return right_count / len(X_test)\n",
    "\n",
    "    def _weight(self):\n",
    "        # linear model\n",
    "        yx = self.Y.reshape(-1, 1) * self.X\n",
    "        self.w = np.dot(yx.T, self.alpha)\n",
    "        return self.w"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "svm = SVM(max_iter=200)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'train done!'"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "svm.fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.64"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "svm.score(X_test, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### scikit-learn实例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "SVC()"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.svm import SVC\n",
    "clf = SVC()\n",
    "clf.fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.96"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "clf.score(X_test, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### sklearn.svm.SVC\n",
    "\n",
    "*(C=1.0, kernel='rbf', degree=3, gamma='auto', coef0=0.0, shrinking=True, probability=False,tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape=None,random_state=None)*\n",
    "\n",
    "参数：\n",
    "\n",
    "- C：C-SVC的惩罚参数C?默认值是1.0\n",
    "\n",
    "C越大，相当于惩罚松弛变量，希望松弛变量接近0，即对误分类的惩罚增大，趋向于对训练集全分对的情况，这样对训练集测试时准确率很高，但泛化能力弱。C值小，对误分类的惩罚减小，允许容错，将他们当成噪声点，泛化能力较强。\n",
    "\n",
    "- kernel ：核函数，默认是rbf，可以是‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’ \n",
    "    \n",
    "    – 线性：u'v\n",
    "    \n",
    "    – 多项式：(gamma*u'*v + coef0)^degree\n",
    "\n",
    "    – RBF函数：exp(-gamma|u-v|^2)\n",
    "\n",
    "    – sigmoid：tanh(gamma*u'*v + coef0)\n",
    "\n",
    "\n",
    "- degree ：多项式poly函数的维度，默认是3，选择其他核函数时会被忽略。\n",
    "\n",
    "\n",
    "- gamma ： ‘rbf’,‘poly’ 和‘sigmoid’的核函数参数。默认是’auto’，则会选择1/n_features\n",
    "\n",
    "\n",
    "- coef0 ：核函数的常数项。对于‘poly’和 ‘sigmoid’有用。\n",
    "\n",
    "\n",
    "- probability ：是否采用概率估计？.默认为False\n",
    "\n",
    "\n",
    "- shrinking ：是否采用shrinking heuristic方法，默认为true\n",
    "\n",
    "\n",
    "- tol ：停止训练的误差值大小，默认为1e-3\n",
    "\n",
    "\n",
    "- cache_size ：核函数cache缓存大小，默认为200\n",
    "\n",
    "\n",
    "- class_weight ：类别的权重，字典形式传递。设置第几类的参数C为weight*C(C-SVC中的C)\n",
    "\n",
    "\n",
    "- verbose ：允许冗余输出？\n",
    "\n",
    "\n",
    "- max_iter ：最大迭代次数。-1为无限制。\n",
    "\n",
    "\n",
    "- decision_function_shape ：‘ovo’, ‘ovr’ or None, default=None3\n",
    "\n",
    "\n",
    "- random_state ：数据洗牌时的种子值，int值\n",
    "\n",
    "\n",
    "主要调节的参数有：C、kernel、degree、gamma、coef0。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第7章支持向量机-习题\n",
    "\n",
    "### 习题7.1\n",
    "&emsp;&emsp;比较感知机的对偶形式与线性可分支持向景机的对偶形式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答：**  \n",
    "**感知机算法的原始形式：**  \n",
    "给定一个训练数据集$$T=\\{(x_1,y_1),(x_2,y_2),\\cdots,(x_N,y_N)\\}$$其中，$x_i \\in \\mathcal{X} = R^n, y_i \\in \\mathcal{Y}=\\{-1,1\\}, i=1,2,\\cdots,N$，求参数$w,b$，使其为以下损失函数极小化问题的解：$$\\min_{w,b} L(w,b)=-\\sum_{x_i \\in M} y_i(w \\cdot x_i + b)$$其中M为误分类点的集合。  \n",
    "上式等价于：$$\\min_{w,b} L(w,b)=\\sum_{i=1}^N (-y_i(w \\cdot x_i + b))_+$$\n",
    "\n",
    "----\n",
    "\n",
    "**补充：** 合页损失函数$$L(y(w \\cdot x + b)) = [1-y(w \\cdot x + b)]_+$$下标“+”表示以下取正数的函数。$$[z]_+ = \\left\\{\\begin{array}{ll} z, & z>0 \\\\\n",
    "0, & z \\leqslant 0 \n",
    "\\end{array} \\right.$$当样本点$(x_i,y_i)$被正确分类且函数间隔（确信度）$y_i(w \\cdot x_i + b)$大于1时，损失是0，否则损失是$1-y_i(w \\cdot x_i + b)$。\n",
    "\n",
    "----\n",
    "\n",
    "**感知机算法的对偶形式：**  \n",
    "$w,b$表示为$\\langle x_i,y_i \\rangle$的线性组合的形式，求其系数（线性组合的系数）$\\displaystyle w=\\sum_{i=1}^N \\alpha_i y_i x_i, b=\\sum_{i=1}^N \\alpha_i y_i$，满足：$$\n",
    "\\min_{w,b} L(w,b) = \\min_{\\alpha_i} L(\\alpha_i) = \\sum_{i=1}^N (-y_i (\\sum_{j=1}^N \\alpha_j y_j x_j \\cdot x_i + \\sum_{j=1}^N \\alpha_j y_j))_+$$  \n",
    "\n",
    "**线性可分支持向量机的原始问题：**  \n",
    "$$\\begin{array}{cl} \n",
    "\\displaystyle \\min_{w,b} & \\displaystyle \\frac{1}{2} \\|w\\|^2 \\\\\n",
    "\\text{s.t.} & y_i(w \\cdot x_i + b) -1 \\geqslant 0, i=1,2,\\cdots,N\n",
    "\\end{array}$$  \n",
    "\n",
    "**线性可分支持向量机的对偶问题：**  \n",
    "$$\\begin{array}{cl} \n",
    "\\displaystyle \\max_{\\alpha} & \\displaystyle -\\frac{1}{2} \\sum_{i=1}^N \\sum_{j=1}^N \\alpha_i \\alpha_j y_i y_j (x_i \\cdot x_j) + \\sum_{i=1}^N \n",
    "alpha_i \\\\\n",
    "\\text{s.t.} & \\displaystyle \\sum_{i=1}^N \\alpha_i y+i = 0 \\\\\n",
    "& \\alpha \\geqslant 0, i=1,2,\\cdots,N\n",
    "\\end{array}$$根据书上**定理7.2**，可得$\\displaystyle w^*=\\sum_{i=1}^N \\alpha_i^* y_j x_i, b^*=y_i-\\sum_{i=1}^N \\alpha^* y_i (x_i \\cdot x_j)$，可以看出$w,b$实质上也是将其表示为$\\langle x_i, x_j\\rangle$的线性组合形式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 习题7.2\n",
    "\n",
    "&emsp;&emsp;已知正例点$x_1=(1,2)^T,x_2=(2,3)^T,x_3=(3,3)^T$，负例点$x_4=(2,1)^T,x_5=(3,2)^T$，试求最大间隔分离平面和分类决策函数，并在图中挂出分离超平面、间隔边界及支持向量。  \n",
    "\n",
    "**解答：**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "w = [[-1.  2.]]\n",
      "b = [-2.]\n",
      "support vectors = [[3. 2.]\n",
      " [1. 2.]\n",
      " [3. 3.]]\n"
     ]
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "from sklearn.svm import SVC\n",
    "\n",
    "# 加载数据\n",
    "X = [[1, 2], [2, 3], [3, 3], [2, 1], [3, 2]]\n",
    "y = [1, 1, 1, -1, -1]\n",
    "\n",
    "# 训练SVM模型\n",
    "clf = SVC(kernel='linear', C=10000)\n",
    "clf.fit(X, y)\n",
    "\n",
    "print(\"w =\", clf.coef_)\n",
    "print(\"b =\", clf.intercept_)\n",
    "print(\"support vectors =\", clf.support_vectors_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**最大间隔分离超平面：**$-x^{(1)}+2x^{(2)}-2=0$  \n",
    "**分类决策函数：**$f(x)=\\text{sign}(-x^{(1)}+2x^{(2)}-2)$  \n",
    "**支持向量：**$x_1=(3,2)^T,x_2=(1,2)^T, x_3=(3,3)^T$  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# 绘制数据点\n",
    "color_seq = ['red' if v == 1 else 'blue' for v in y]\n",
    "plt.scatter([i[0] for i in X], [i[1] for i in X], c=color_seq)\n",
    "# 得到x轴的所有点\n",
    "xaxis = np.linspace(0, 3.5)\n",
    "w = clf.coef_[0]\n",
    "# 计算斜率\n",
    "a = -w[0] / w[1]\n",
    "# 得到分离超平面\n",
    "y_sep = a * xaxis - (clf.intercept_[0]) / w[1]\n",
    "# 下边界超平面\n",
    "b = clf.support_vectors_[0]\n",
    "yy_down = a * xaxis + (b[1] - a * b[0])\n",
    "# 上边界超平面\n",
    "b = clf.support_vectors_[-1]\n",
    "yy_up = a * xaxis + (b[1] - a * b[0])\n",
    "# 绘制超平面\n",
    "plt.plot(xaxis, y_sep, 'k-')\n",
    "plt.plot(xaxis, yy_down, 'k--')\n",
    "plt.plot(xaxis, yy_up, 'k--')\n",
    "# 绘制支持向量\n",
    "plt.xlabel('$x^{(1)}$')\n",
    "plt.ylabel('$x^{(2)}$')\n",
    "plt.scatter(clf.support_vectors_[:, 0],\n",
    "            clf.support_vectors_[:, 1],\n",
    "            s=150,\n",
    "            facecolors='none',\n",
    "            edgecolors='k')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 习题7.3\n",
    "\n",
    "&emsp;&emsp;线性支持向量机还可以定义为以下形式：$$\\begin{array}{cl} \n",
    "\\displaystyle \\min_{w,b,\\xi} & \\displaystyle \\frac{1}{2} \\|w\\|^2 + C \\sum_{i=1}^N \\xi_i^2 \\\\\n",
    "\\text{s.t.} & y_i(w \\cdot x_i + b) \\geqslant 1 - \\xi_i, i=1,2,\\cdots, N \\\\\n",
    "& \\xi_i \\geqslant 0, i=1,2,\\cdots, N\n",
    "\\end{array}$$试求其对偶形式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答：**  \n",
    "根据支持向量机的对偶算法，得到对偶形式，由于不能消去变量$\\xi_i$的部分，所以拉格朗日因子也包含$\\beta_i$。  \n",
    "拉格朗日函数为：$$L(w,b,\\xi, \\alpha, \\beta) = \\frac{1}{2} \\|w\\|^2 + C \\sum_{i=1}^N \\xi_i^2 + \\sum_{i=1}^N \\alpha_i - \\sum_{i=1}^N \\alpha_i \\xi_i - \\sum_{i=1}^N \\alpha_i y_i (w \\cdot x_i + b) - \\sum_{i=1}^N \\beta_i \\xi_i$$  \n",
    "分别求$w,b,\\xi$的偏导数：$$\\left \\{ \\begin{array}{l}\n",
    "\\displaystyle \\nabla_w L  = w - \\sum_{i=1}^N \\alpha_i y_i x_i = 0 \\\\ \n",
    "\\displaystyle \\nabla_b L  =  -\\sum_{i=1}^N \\alpha_i y_i = 0 \\\\\n",
    "\\nabla_{\\xi} L  = 2C \\xi_i - \\alpha_i - \\beta_i = 0 \n",
    "\\end{array} \\right.$$化简可得：$$\\left \\{ \\begin{array}{l}\n",
    "\\displaystyle w = \\sum_{i=1}^N \\alpha_i y_i x_i = 0 \\\\ \n",
    "\\displaystyle \\sum_{i=1}^N \\alpha_i y_i = 0 \\\\\n",
    "2C \\xi_i - \\alpha_i - \\beta_i = 0 \n",
    "\\end{array} \\right.$$  \n",
    "可解得：$$\n",
    "L=-\\frac{1}{2} \\sum_{i=1}^N \\sum_{j=1}^N \\alpha_i \\alpha_j y_i y_j (x_i \\cdot x_{j})+\\sum_{i=1}^N \\alpha_i-\\frac{1}{4C}\\sum_{i=1}^N(\\alpha_i+\\beta_i)^2$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 习题7.4\n",
    "\n",
    "&emsp;&emsp;证明内积的正整数幂函数：$$K(x,z)=(x\\cdot z)^p$$是正定核函数，这里$p$是正整数，$ x,z\\in R^n$。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**解答：**  \n",
    "根据书中第121页定理7.5可知，如果需要证明$K(x,z)$是正定核函数，即证明$K(x,z)$对应的Gram矩阵$K=\\left[ K(x_i,x_j) \\right]_{m \\times m}$是半正定矩阵。  \n",
    "对任意$c_1,c_2,\\cdots,c_m \\in \\mathbf{R}$，有$$\\begin{aligned} \n",
    "\\sum_{i,j=1}^m c_i c_j K(x_i,x_j) \n",
    "&= \\sum_{i,j=1}^m c_i c_j (x_i \\cdot x_j)^p \\\\\n",
    "&= \\left(\\sum_{i=1}^m c_i x_i \\right)\\left(\\sum_{j=1}^m c_i x_j \\right)(x_i \\cdot x_j)^{p-1} \\\\\n",
    "&= \\Bigg\\|\\left( \\sum_{i=1}^m c_i x_i \\right)\\Bigg\\|^2 (x_i \\cdot x_j)^{p-1}\n",
    "\\end{aligned}$$\n",
    "$\\because p$是正整数，$p \\geqslant 1$  \n",
    "$\\therefore p-1 \\geqslant 0 \\Rightarrow (x_i \\cdot x_j)^{p-1} \\geqslant 0$  \n",
    "故$\\displaystyle \\sum_{i,j=1}^m c_i c_j K(x_i,x_j) \\geqslant 0$，即Gram矩阵是半正定矩阵。  \n",
    "根据定理7.5，可得$K(x,z)$是正定核函数，得证。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----\n",
    "参考代码：https://github.com/wzyonggege/statistical-learning-method\n",
    "\n",
    "本文代码更新地址：https://github.com/fengdu78/lihang-code\n",
    "\n",
    "习题解答：https://github.com/datawhalechina/statistical-learning-method-solutions-manual\n",
    "\n",
    "中文注释制作：机器学习初学者公众号：ID:ai-start-com\n",
    "\n",
    "配置环境：python 3.5+\n",
    "\n",
    "代码全部测试通过。\n",
    "![gongzhong](../gongzhong.jpg)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
